/////////////////////////////////////
// InfiniteMask.h
//
/////////////////////////////////////

/// Defines / Macros ----------------

#pragma once

/// Includes ------------------------

// Standards
#include <vector>

/// Class ---------------------------

namespace nkGraphics
{
	template<typename T>
	class InfiniteMask
	{
		public :
	
			// Constructors, destructor
			InfiniteMask () = default ;

			InfiniteMask (const InfiniteMask<T>& other)
			:	_vals (other._vals)
			{
				// Nothing to do
			}

			InfiniteMask (InfiniteMask<T>&& other)
			:	_vals (std::move(other._vals))
			{
				// Nothing to do
			}

			~InfiniteMask () = default ;

			// Resize 
			void resize (unsigned int size)
			{
				_vals.resize(size) ;
			}

			// Adds a value
			void setVal (unsigned int index, T value)
			{
				// On ne change que dans quelques cas
				// Notamment on ignore un bit à false plus gros que notre taille
				// Soit on s'assure qu'on a bien tout ce qu'il faut
				if (index >= _vals.size() && value >= 0)
				{
					// On append à la fin
					_vals.resize(index + 1, -1) ;
					// On met
					_vals[index] = value ;
				}

				// Sinon un bit à changer au milieu
				else if (index < _vals.size() && value >= 0)
					_vals[index] = value ;

				// Toujours un bit = changer au milieu...
				// Mais on réduit au maximum jusqu'au dernier bit set pour avoir un tableau minimal en tout temps
				else if (index < _vals.size() && value < 0)
				{
					// On va le mettre à false
					_vals[index] = -1 ;

					// On parcourt la tableau en reverse jusqu'à trouver un bit set
					unsigned int zeroCounter = 0 ;

					for (size_t i = _vals.size() - 1 ; i < _vals.size() ; --i)
					{
						// Si on a un bit mis, alors on break
						if (_vals[i] != -1)
							break ;

						// Sinon on continue avec notre compteur mis à jour
						++zeroCounter ;
					}

					// On vérifie
					if (zeroCounter)
					{
						// Alors on a quelques bits ・virer
						_vals.resize(_vals.size() - zeroCounter) ;
					}
				}
			}

			// Resets
			void clear ()
			{
				// On clear notre array juste
				_vals.clear() ;
			}

			// Retrieve a value
			T getVal (unsigned int index) const
			{
				if (index >= _vals.size())
					return false ;

				return _vals[index] ;
			}

			// Get order, aka highest index of set value
			unsigned int getOrder () const
			{
				return (unsigned int)_vals.size() ;
			}

			// Comparison
			bool checkSubPartEquality (const InfiniteMask<T>& other, unsigned int begin, unsigned int end) const
			{
				// On compare mais pas sur tout la longueur
				// On vérifie le début
				if (begin >= end)
					return false ;

				// Maintenant on démarre et on try
				// On garde les ordres donnés
				unsigned int aOrder = getOrder() ;
				unsigned int bOrder = other.getOrder() ;
				// On attaque
				for (unsigned int i = begin ; i < end ; ++i)
				{
					// Vérifions si un truc est différent si les deux sont définis
					if (aOrder > i && bOrder > i)
					{
						// On les compare juste
						if (_vals[i] != other._vals[i])
							return false ;
					}
					// Sinon l'un n'est pas définit, auquel cas l'autre doit être zéro tout le long
					else if (aOrder > i)
					{
						if (_vals[i])
							return false ;
					}
					else if (bOrder > i)
					{
						if (other._vals[i])
							return false ;
					}
					// Sinon les deux sont lessivés... On peut sortir
					else
						break ;
				}

				// Rien trouvé de différent
				return true ;
			}

			// Check if one mask covers all entries of another
			bool checkSubPartFill (const InfiniteMask<T>& other, unsigned int begin, unsigned int end) const
			{
				// Check range we should be having
				// Get orders of both items
				unsigned int aOrder = getOrder() ;
				unsigned int bOrder = other.getOrder() ;
				unsigned int maxOrder = std::max(aOrder, bOrder) ;

				// Correct indices
				// Same index means all remaining from start
				if (begin >= maxOrder || begin > end)
					return false ;
				else if (begin == end)
					end = maxOrder ;

				// On attaque
				for (unsigned int i = begin ; i < end ; ++i)
				{
					// Vérifions si un truc est différent si les deux sont définis
					if (aOrder > i && bOrder > i)
					{
						// On vérifie juste que les deux sont sets
						if (other._vals[i] >= 0 && _vals[i] < 0)
							return false ;
					}
					// Sinon l'un n'est pas défini, auquel cas l'autre doit être zéro tout le long
					else if (aOrder > i)
					{
						// Other mask is covered
						return true ;
					}
					else if (bOrder > i)
					{
						// Means we could not cover all values of the other mask, maybe
						if (other._vals[i] >= 0)
							return false ;
					}
					// Sinon les deux sont lessivés... On peut sortir
					else
						break ;
				}

				// Rien trouvé de différent
				return true ;
			}

			// Merge with another mask
			void mergeWith (const InfiniteMask<T>& other, bool overrideValues = false)
			{
				// First check which one is the biggest to resize to the right size
				size_t minSize = std::min(_vals.size(), other._vals.size()) ;

				if (minSize == _vals.size())
				{
					_vals.resize(other._vals.size(), -1) ;
					minSize = _vals.size() ;
				}

				// Now loop over the values and mix them together
				for (size_t i = 0 ; i < minSize ; ++i)
				{
					if ((_vals[i] < 0 || overrideValues) && other._vals[i] >= 0)
						_vals[i] = other._vals[i] ;
				}
			}

			// Access
			typename std::vector<T>::reference operator[] (size_t index)
			{
				return _vals[index] ;
			}

			T operator[] (size_t index) const
			{
				return getVal((unsigned int)index) ;
			}

			// Comparison
			bool operator== (const InfiniteMask<T>& other) const
			{
				// Comparaison des tailles
				if (other._vals.size() != _vals.size())
					return false ;

				// Eh bien on doit comparer toutes les entr馥s
				for (size_t i = 0 ; i < _vals.size() ; ++i)
				{
					if (_vals[i] != other._vals[i])
						return false ;
				}

				// On a tout pareil
				return true ;
			}

			bool operator!= (const InfiniteMask<T>& other) const
			{
				// Comparaison des tailles
				if (other._vals.size() != _vals.size())
					return true ;

				// Eh bien on doit comparer toutes les entr馥s
				for (size_t i = 0 ; i < _vals.size() ; ++i)
				{
					// Une différence, c'est gagné
					if (_vals[i] != other._vals[i])
						return true ;
				}

				// On a tout pareil
				return false ;
			}

			bool operator< (const InfiniteMask<T>& other) const
			{
				// On compare les tailles... Car on garde nos tableaux le plus shrink
				if (other._vals.size() > _vals.size())
					return true ;
				else if (other._vals.size() < _vals.size())
					return false ;

				// Sinon il faut qu'on chope le premier duo de bit initiaux
				for (size_t i = _vals.size() - 1 ; i < _vals.size() ; --i)
				{
					if (other._vals[i] != _vals[i])
					{
						// Alors... Notre bit est soit dominant, soit pas dominant
						return _vals[i] < other._vals[i] ;
					}
				}

				// Nous sommes égaux sinon...
				return false ;
			}

			// Assignment
			InfiniteMask& operator= (const InfiniteMask<T>& other)
			{
				// On a juste à copier le tableau
				_vals = other._vals ;

				return *this ;
			}

			InfiniteMask& operator= (InfiniteMask<T>&& other)
			{
				// On swap
				_vals.swap(other._vals) ;

				return *this ;
			}

		private :

			// The values held
			std::vector<T> _vals ;
	} ;

	// Typedefs over known types
	typedef InfiniteMask<char> InfiniteByteMask ;
	typedef InfiniteMask<short> InfiniteShortMask ;
	typedef InfiniteMask<int> InfiniteIntMask ;
	typedef InfiniteMask<unsigned long long> InfiniteInt64Mask ;
}